package com.xuecheng.auth.service;

import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xuecheng.framework.client.XcServiceList;
import com.xuecheng.framework.domain.ucenter.ext.AuthToken;
import com.xuecheng.framework.domain.ucenter.response.AuthCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author: gongfy
 * @create: 2020/8/26 15:16
 * @Description:  用户认证service
 */
@Service
@Slf4j
public class AuthService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Value("${auth.tokenValiditySeconds}") // redis过期时间
    int tokenValiditySeconds;

    /**
    * @Author: gongfy
    * @Param: username 登录账户 password 登录密码 clientId客户端id clientSecret客户端密码
    * @Return:  LoginResult 登录结果 成功则返回token令牌
    * @Date: 2020/8/26 15:21
    * @Description: 用户登录认证
    **/
    public AuthToken login(String username, String password, String clientId, String clientSecret) {
        //1.访问spring security申请令牌
        AuthToken authToken = this.applyToken(username, password, clientId, clientSecret);
        if (authToken == null){
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }
        //2.将令牌存入redis
        // String content = JSONUtils.toJSONString(authToken); // todo java.lang.IllegalArgumentException: not support type : class com.xuecheng.framework.domain.ucenter.ext.AuthToken
        String content = JSON.toJSONString(authToken); // 要使用fastjson转换
        Boolean saveTokenResult = this.saveTokenToRedis(authToken.getJti_token(), content, tokenValiditySeconds);
        if (!saveTokenResult){
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
        }
        return authToken;
    }

    //将令牌存入redis
    public Boolean saveTokenToRedis(String token,String content,int ttl){
        //令牌名称
        String key = "user_token"+token;
        stringRedisTemplate.boundValueOps(key).set(content,ttl, TimeUnit.SECONDS);
        //获取过期时间  没有过期就是存储成功
        Long expire = stringRedisTemplate.getExpire(key);
        return expire>0;
    }

    // 从缓存中查询令牌
    public AuthToken findTokenFromRedis(String jtiToken){
        String key = "user_token"+jtiToken;
        String tokenString = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(tokenString)){
            try {
                AuthToken authToken = JSON.parseObject(tokenString, AuthToken.class);// json转对象
                return authToken;
            } catch (Exception e) {
                log.error("getUserToken from redis and execute JSON.parseObject error{}",e.getMessage());
                e.printStackTrace();
            }
        }
        return null;
    }
    /**
     * @Author: gongfy
     * @Date: 2020/8/26 15:24
     * @Description: 清除缓存中存储的令牌
     **/
    public Boolean delTokenFromRedis(String jitToken) {
        String key = "user_token" + jitToken;
        //清除redis中的token
        stringRedisTemplate.delete(key);
        return true;
    }
    //获取  clientId 客户端id，clientSecret客户端密码
    private String getHttpBasic(String clientId ,String clientSecret) {
        String str = clientId+":"+clientSecret;
        byte[] encode = Base64Utils.encode(str.getBytes());
        return "Basic "+ new String(encode);
    }

    //访问spring security申请令牌
    public AuthToken applyToken(String username, String password, String clientId, String clientSecret){
        //1.封装数据
        //通过负载均衡 从eureka获取认证服务实例并获取url
        ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
        if (serviceInstance == null){
            log.error("choose an auth instance fail");
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_AUTHSERVER_NOTFOUND);
        }
        String authUrl = serviceInstance.getUri()+"/auth/oauth/token";
        //封装body
        MultiValueMap<String,String> body = new LinkedMultiValueMap<>();
        body.add("username",username);
        body.add("password",password);
        body.add("grant_type","password"); // oauth2授权类型 password密码模式
        //封装headers（需要http Basic认证）
        MultiValueMap<String,String> headers = new LinkedMultiValueMap<>();
        String httpBasic = this.getHttpBasic(clientId, clientSecret);
        headers.add("Authorization",httpBasic);
        //封装httpEntity
        HttpEntity<MultiValueMap<String,String>> httpEntity = new HttpEntity(body,headers);

        //restTemplate当遇到400或401响应时候也不要抛出异常，也要正常返回值
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
            @Override   // 重写父类方法，过滤判断抛异常   restTemplate响应对象 ClientHttpResponse
            public void handleError(ClientHttpResponse response) throws IOException {
                if(response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401){
                    super.handleError(response);
                }
            }
        });
        //2.访问spring security申请令牌
        Map resultMap = null;
        try {
            ResponseEntity<Map> exchange = restTemplate.exchange(authUrl, HttpMethod.POST, httpEntity, Map.class);
            resultMap = exchange.getBody();
            if (resultMap == null || resultMap.get("access_token") == null ||
                    resultMap.get("refresh_token") == null ||resultMap.get("jti") == null ){
                String error_description = String.valueOf(resultMap.get("error_description"));
                if (StringUtils.isNotEmpty(error_description)){
                    if ("坏的凭证".equals(error_description)) {
                        ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR); //账号或密码错误
                    } else if (error_description.indexOf("UserDetailsService returned null") >= 0) {
                        ExceptionCast.cast(AuthCode.AUTH_ACCOUNT_NOTEXISTS); // 账号不存在
                    }
                }
                return null;
            }
        } catch (RestClientException e) {
            e.printStackTrace();
            log.error("request oauth_token_password error: {}",e.getMessage());
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }
        AuthToken authToken = new AuthToken();
        authToken.setJti_token(String.valueOf(resultMap.get("jti"))); //访问令牌(jwt)
        authToken.setJwt_token(String.valueOf(resultMap.get("access_token")));//短令牌 jti作为用户的身份标识
        authToken.setRefresh_token(String.valueOf(resultMap.get("refresh_token")));//刷新令牌
        return authToken;
    }

}
